package com.klxedu.ms.gateway.security.config;

import javax.servlet.Filter;

import org.springframework.amqp.core.FanoutExchange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

import com.klxedu.ms.gateway.AuthServerProperties;
import com.klxedu.ms.gateway.security.access.IResourceService;
import com.klxedu.ms.gateway.security.authentication.CustomAuthenticationUserDetailsService;
import com.klxedu.ms.gateway.security.authentication.CustomWebAuthenticationDetailsSource;
import com.klxedu.ms.gateway.security.authentication.IAuthenticateAction;
import com.klxedu.ms.gateway.security.authentication.IAuthenticateRoles;
import com.klxedu.ms.gateway.security.entrypoint.BaseAuthenticationEntryPoint;
import com.klxedu.ms.gateway.security.entrypoint.CaptchaEntryPoint;
import com.klxedu.ms.gateway.security.filter.CaptchaFilter;
import com.klxedu.ms.gateway.security.filter.CustomBasicAuthenticationFilter;
import com.klxedu.ms.gateway.security.filter.CustomConcurrentSessionFilter;
import com.klxedu.ms.gateway.security.filter.CustomSecurityFilter;
import com.klxedu.ms.gateway.security.logout.CustomLogoutSuccessHandler;
import com.klxedu.ms.gateway.security.session.CustomSessionInformationExpiredStrategy;
import com.klxedu.ms.gateway.security.session.CustomSessionRegistryImpl;

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Bean
	public FanoutExchange userEvent() {
		return new FanoutExchange("userEventExchange");
	}

	@Override
	@Bean
	protected AuthenticationManager authenticationManager() throws Exception {
		return super.authenticationManager();
	}

	@Autowired
	private CustomSecurityFilter mySecurityFilter;

	@Bean
	public SessionRegistry sessionRegistry() {
		return new CustomSessionRegistryImpl();
	}

	@Bean
	public AuthServerProperties authServerProperties() {
		return new AuthServerProperties();
	}

	@Bean
	public Filter captchaFile() {
		return new CaptchaFilter(new CaptchaEntryPoint());
	}

	@Bean
	public IResourceService resourceService()
			throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		Assert.notNull(authServerProperties().getResourceService(), "A ResourceService is required");
		return (IResourceService) Class.forName(authServerProperties().getResourceService()).newInstance();
	}

	@Bean
	public IAuthenticateRoles authenticateRoles()
			throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		Assert.notNull(authServerProperties().getRoleService(), "A RoleService is required");
		return (IAuthenticateRoles) Class.forName(authServerProperties().getRoleService()).newInstance();
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		CustomBasicAuthenticationFilter basicAuthenticationFilterExtFilter = new CustomBasicAuthenticationFilter(
				authenticationManagerBean());
		basicAuthenticationFilterExtFilter.setAuthenticationDetailsSource(new CustomWebAuthenticationDetailsSource());
		CustomConcurrentSessionFilter customConcurrentSessionFilter = new CustomConcurrentSessionFilter(
				sessionRegistry(), new CustomSessionInformationExpiredStrategy());
		AuthServerProperties authServerProperties = authServerProperties();
		// @formatter:off
		http.headers().frameOptions().disable();
		
		http.httpBasic().authenticationEntryPoint(new BaseAuthenticationEntryPoint()).and()
				.addFilterBefore(mySecurityFilter, FilterSecurityInterceptor.class)
				.addFilterBefore(captchaFile(), BasicAuthenticationFilter.class)
				.addFilterBefore(basicAuthenticationFilterExtFilter, BasicAuthenticationFilter.class)
				.addFilterBefore(customConcurrentSessionFilter, ConcurrentSessionFilter.class).logout()
				.invalidateHttpSession(false).logoutSuccessHandler(new CustomLogoutSuccessHandler())
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")).and().authorizeRequests()
				.antMatchers("/", "/login", "/user/generateCaptchaNum", "/user/generateCaptchaImage",
						"/user/validateCaptcha", "/user/event/visit")
				.permitAll().antMatchers("/**/open/**", "/**/open/**/**").permitAll()
				.antMatchers("/workbench/*", "/workbench/**/*").hasRole("ADMIN").anyRequest().authenticated().and()
				.csrf()
				.ignoringAntMatchers(authServerProperties.getCsrf() != null
						? authServerProperties.getCsrf().getIgnoringAntMatchers().toArray(new String[0])
						: new String[0])
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and().sessionManagement()
				.maximumSessions(authServerProperties.getMaximumSessions()).sessionRegistry(sessionRegistry());
		// @formatter:on
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		Assert.notNull(authServerProperties().getAuthenticateAction(), "A AuthenticateAction is required");
		for (int i = 0; i < authServerProperties().getAuthenticateAction().size(); i++) {
			DaoAuthenticationProvider defaultAuthenticationProvider = new DaoAuthenticationProvider();
			defaultAuthenticationProvider.setHideUserNotFoundExceptions(false);
			IAuthenticateAction authenticateAction = (IAuthenticateAction) Class
					.forName(authServerProperties().getAuthenticateAction().get(i)).newInstance();
			defaultAuthenticationProvider.setUserDetailsService(
					new CustomAuthenticationUserDetailsService(authenticateAction, authenticateRoles()));
			defaultAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
			auth.authenticationProvider(defaultAuthenticationProvider);
		}
	}

}
